@gpc-cli/core 0.10.3 → 0.10.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +22 -2
- package/dist/index.js +226 -46
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -79,4 +79,4 @@ const analysis = await analyzeBundle("./app.aab");
|
|
|
79
79
|
|
|
80
80
|
## Licensing
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
MIT-licensed open source and free to use. Source code is on GitHub at [yasserstudio/gpc](https://github.com/yasserstudio/gpc).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OutputFormat, ResolvedConfig, WebhookConfig } from '@gpc-cli/config';
|
|
2
2
|
import { AuthClient } from '@gpc-cli/auth';
|
|
3
3
|
import { GpcPlugin, PluginManifest, CommandEvent, CommandResult, PluginError, RequestEvent, ResponseEvent, PluginCommand } from '@gpc-cli/plugin-sdk';
|
|
4
|
-
import { PlayApiClient, EditCommitOptions, Track, ExternallyHostedApk, ExternallyHostedApkResponse, DeobfuscationFileType, UploadProgressEvent, ResumableUploadOptions, Listing, ImageType, CountryAvailability, Image, AppDetails, Review, ReviewReplyResponse, Subscription, SubscriptionOffer, OffersListResponse, BasePlanMigratePricesRequest, InAppProduct, ConsumptionUsageEvent, RefundPreference, Order, SubscriptionDeferResponse, SubscriptionsV2DeferResponse, ProductPurchase, ProductPurchaseV2, SubscriptionPurchaseV2, VoidedPurchase, UsersApiClient, User, DeveloperLevelPermission, Grant, MetricRow, ReportingDimension, ReportingAggregation, VitalsMetricSet, ReportingApiClient, AnomalyDetectionResponse, MetricSetResponse, ErrorIssuesResponse, ConvertRegionPricesResponse, ReportType, StatsDimension, Testers, AppRecoveryTargeting, AppRecoveryAction, CreateAppRecoveryActionRequest, DataSafetyResponse, DataSafetyRequest,
|
|
4
|
+
import { PlayApiClient, EditCommitOptions, Track, ExternallyHostedApk, ExternallyHostedApkResponse, DeobfuscationFileType, UploadProgressEvent, ResumableUploadOptions, Listing, ImageType, CountryAvailability, Image, AppDetails, Review, ReviewReplyResponse, Subscription, SubscriptionOffer, OffersListResponse, BasePlanMigratePricesRequest, InAppProduct, ConsumptionUsageEvent, RefundPreference, Order, SubscriptionDeferResponse, SubscriptionsV2DeferResponse, ProductPurchase, ProductPurchaseV2, SubscriptionPurchaseV2, VoidedPurchase, UsersApiClient, User, DeveloperLevelPermission, Grant, MetricRow, ReportingDimension, ReportingAggregation, VitalsMetricSet, ReportingApiClient, AnomalyDetectionResponse, MetricSetResponse, ErrorIssuesResponse, ConvertRegionPricesResponse, ReportType, StatsDimension, Testers, AppRecoveryTargeting, AppRecoveryAction, CreateAppRecoveryActionRequest, DataSafetyResponse, DataSafetyRequest, ExternalTransactionRefund, ExternalTransaction, DeviceTierConfig, OneTimeOffer, OneTimeProduct, OneTimeOffersListResponse, OneTimeProductsListResponse, GamesConfigClient, AchievementConfiguration, LeaderboardConfiguration, AchievementConfigurationListResponse, GamesApiClient, Achievement, LeaderboardConfigurationListResponse, Leaderboard, ImageConfiguration, EnterpriseApiClient, CustomApp, GeneratedApksPerSigningKey, Bundle } from '@gpc-cli/api';
|
|
5
5
|
|
|
6
6
|
declare class GpcError extends Error {
|
|
7
7
|
readonly code: string;
|
|
@@ -693,6 +693,7 @@ declare function reviewOrderRefund(client: PlayApiClient, packageName: string, o
|
|
|
693
693
|
declare const PERMISSION_PROPAGATION_WARNING = "Note: Permission changes may take up to 48 hours to propagate.";
|
|
694
694
|
interface ListUsersOptions {
|
|
695
695
|
pageToken?: string;
|
|
696
|
+
/** @deprecated Ignored: Google does not paginate users.list. */
|
|
696
697
|
pageSize?: number;
|
|
697
698
|
limit?: number;
|
|
698
699
|
nextPage?: string;
|
|
@@ -1034,6 +1035,21 @@ declare function importDataSafety(client: PlayApiClient, packageName: string, fi
|
|
|
1034
1035
|
declare function createExternalTransaction(client: PlayApiClient, packageName: string, data: ExternalTransaction, externalTransactionId: string): Promise<ExternalTransaction>;
|
|
1035
1036
|
declare function getExternalTransaction(client: PlayApiClient, packageName: string, transactionId: string): Promise<ExternalTransaction>;
|
|
1036
1037
|
declare function refundExternalTransaction(client: PlayApiClient, packageName: string, transactionId: string, refundData: ExternalTransactionRefund): Promise<ExternalTransaction>;
|
|
1038
|
+
interface ExternalTransactionRefundOptions {
|
|
1039
|
+
full?: boolean;
|
|
1040
|
+
/** Pre-tax amount in micros, e.g. "1990000" for 1.99. */
|
|
1041
|
+
partialAmount?: string;
|
|
1042
|
+
currency?: string;
|
|
1043
|
+
refundId?: string;
|
|
1044
|
+
/** When the refund happened; defaults to now. */
|
|
1045
|
+
refundTime?: string;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Build a refund request from CLI options. Google requires refundTime on every
|
|
1049
|
+
* refund and a unique refundId on each partial refund; a refund must name its
|
|
1050
|
+
* kind explicitly so a mistyped amount can never become a full refund.
|
|
1051
|
+
*/
|
|
1052
|
+
declare function buildExternalTransactionRefund(options: ExternalTransactionRefundOptions, now?: Date): ExternalTransactionRefund;
|
|
1037
1053
|
|
|
1038
1054
|
declare function listDeviceTiers(client: PlayApiClient, packageName: string): Promise<DeviceTierConfig[]>;
|
|
1039
1055
|
declare function getDeviceTier(client: PlayApiClient, packageName: string, configId: string): Promise<DeviceTierConfig>;
|
|
@@ -1245,6 +1261,10 @@ interface PreflightContext {
|
|
|
1245
1261
|
aabPath?: string;
|
|
1246
1262
|
manifest?: ParsedManifest;
|
|
1247
1263
|
zipEntries?: ZipEntryInfo[];
|
|
1264
|
+
/** True for an AAB, false for an APK (decided by the reader from the file type). */
|
|
1265
|
+
isAppBundle?: boolean;
|
|
1266
|
+
/** AAB modules that are not part of the first download (on-demand, fast-follow). */
|
|
1267
|
+
deferredModules?: string[];
|
|
1248
1268
|
/** First 4096 bytes of each .so file, keyed by ZIP entry path. */
|
|
1249
1269
|
nativeLibHeaders?: EntryHeaderMap;
|
|
1250
1270
|
metadataDir?: string;
|
|
@@ -1981,4 +2001,4 @@ declare function readinessBadgeColor(grade: ReadinessGrade): string;
|
|
|
1981
2001
|
declare function readinessBadgeUrl(score: ReadinessScore): string;
|
|
1982
2002
|
declare function readinessBadgeMarkdown(score: ReadinessScore): string;
|
|
1983
2003
|
|
|
1984
|
-
export { ApiError, type ApiSigningFingerprint, type AppInfo, type AppStatus, type ApplyReleaseNotesResult, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type BundlesWaitOptions, type ChangelogEntry, type ChecklistInput, type ChecklistItem, type ChecklistResult, type CommandContext, type CommitCluster, type CommitResult, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, DEFAULT_WATCH_THRESHOLDS, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DiscoveredPlugin, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FinancialReportDownload, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GameConfigDiff, type GamesPushOptions, type GamesSyncResult, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageSyncDetail, type ImageSyncOptions, type ImageSyncResult, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type KeystoreFingerprint, type ListGamesConfigOptions, type ListIapOptions, type ListReportsResult, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, REVIEW_SORT_PRESETS, type RawCommit, type ReadinessBreakdownEntry, type ReadinessGrade, type ReadinessScore, type ReadinessSignal, type ReadinessSignalStatus, type RefundReviewPreference, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ReportObject, type ReportsAuth, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewOrderRefundOptions, type ReviewOrderRefundResult, type ReviewSortPreset, type ReviewsFilterOptions, type RevocationRefundType, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type SigningConsistencyResult, type SigningKeyComparison, type Spinner, type StatsReportDownload, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, type UploadResult, VALID_WATCH_METRICS, type ValidateCheck, type ValidateOptions, type ValidateResult, type ValidatedPluginApproval, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchAction, type WatchCallbacks, type WatchConfig, type WatchEvent, type WatchMetric, type WatchOptions, type WatchRollout, type WatchSummary, type WatchVitalReading, type WatchVitalsOptions, type WebhookPayload, type ZipCsvEntry, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, annotateListResult, applyReleaseNotes, assignRelease, batchGetOrders, batchSyncInAppProducts, buildChecklist, buildLocaleBundle, bundleToReleaseNotes, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkSigningConsistency, checkThreshold, classifyError, clearAuditLog, commitWithRescue, compareBundles, compareFingerprints, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAchievementConfig, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createLeaderboardConfig, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeCsvText, decodeNotification, decodeStatsCsv, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteAchievementConfig, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteLeaderboardConfig, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffAchievementConfig, diffLeaderboardConfig, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPluginEntries, discoverPlugins, dispatchWebhook, downloadFinancialReport, downloadGeneratedApk, downloadReportObject, downloadStatsReport, exportImages, exportReviews, extractCsvEntriesFromZip, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, findBundle, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatMoney, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAchievementConfig, getAllScannerNames, getApiSigningFingerprint, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getKeystoreFingerprint, getLeaderboardConfig, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsMemoryBitmap, getVitalsMemoryRss, getVitalsOverview, getVitalsRendering, getVitalsStartup, gradeFromPercent, gunzipIfNeeded, handleBreach, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isGzip, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, isVersionedNotesDir, isZip, lintListing, lintListings, lintLocalListings, listAchievementConfigs, listAchievements, listAuditEvents, listBundles, listDeviceTiers, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboardConfigs, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReportObjects, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, monthToken, moreResultsFooter, normalizeFingerprint, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseKeytoolOutput, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullAchievementConfigs, pullLeaderboardConfigs, pullListings, pushAchievementConfigs, pushLeaderboardConfigs, pushListings, readListingsFromDir, readReleaseNotesForVersion, readReleaseNotesFromDir, readinessBadgeColor, readinessBadgeMarkdown, readinessBadgeUrl, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderChecklistMarkdown, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, reportAccessDeniedError, reportBucketNotFoundError, reportObjectNotFoundError, reportPrefix, resolveAiConfig, resolveLocales, resolveReportsBucket, retryOnUploadNotComplete, reviewOrderRefund, revokeSubscriptionPurchase, runPreflight, runWatch, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, scoreReadiness, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, setAchievementIcon, setLeaderboardIcon, sha256File, sortResults, sortReviews, startTrain, statusHasBreach, syncImages, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAchievementConfig, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateLeaderboardConfig, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateAndCommit, validateBundleForApply, validateImage, validateLanguageCode, validatePackageName, validatePluginForApproval, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, waitForBundle, waitForBundleProcessing, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
|
|
2004
|
+
export { ApiError, type ApiSigningFingerprint, type AppInfo, type AppStatus, type ApplyReleaseNotesResult, type AuditEntry, type BatchSyncResult, type BundleAnalysis, type BundleComparison, type BundleEntry, type BundleSizeCheckResult, type BundleSizeConfig, type BundlesWaitOptions, type ChangelogEntry, type ChecklistInput, type ChecklistItem, type ChecklistResult, type CommandContext, type CommitCluster, type CommitResult, ConfigError, type CreateEnterpriseAppParams, DEFAULT_LIMITS, DEFAULT_MODELS, DEFAULT_PREFLIGHT_CONFIG, DEFAULT_WATCH_THRESHOLDS, type DecodedNotification, type DiffToken, type DiscoverPluginsOptions, type DiscoveredPlugin, type DryRunPublishResult, type DryRunResult, type DryRunUploadResult, type ErrorReason, type ExportImagesOptions, type ExportImagesSummary, type ExternalTransactionRefundOptions, type FastlaneDetection, type FastlaneLane, type FetchChangelogOptions, type FieldLintResult, type FileValidationResult, type FinancialReportDownload, type FindingSeverity, GOOGLE_PLAY_LANGUAGES, type GameConfigDiff, type GamesPushOptions, type GamesSyncResult, type GenerateOptions, type GeneratedChangelog, type GetAppStatusOptions, type GitNotesOptions, type GitReleaseNotes, type GitRunner, GpcError, type ImageSyncDetail, type ImageSyncOptions, type ImageSyncResult, type ImageValidationResult, type InitOptions, type InitResult, type InternalSharingUploadResult, type KeystoreFingerprint, type ListGamesConfigOptions, type ListIapOptions, type ListReportsResult, type ListSubscriptionsOptions, type ListUsersOptions, type ListVoidedOptions, type ListingDiff, type ListingFieldLimits, type ListingLintResult, type ListingsResult, type LoadedPlugin, type LocaleBundle, type LocaleEntry, type MigrationResult, NetworkError, type OneTimeProductDiff, type OutputMode, PERMISSION_PROPAGATION_WARNING, PLACEHOLDER_TEXT, PLAY_STORE_LIMIT, PROVIDER_WHITELIST, type ParsedCommit, type ParsedManifest, type ParsedMonth, type PlayStoreFormat, type PlayStoreRenderOptions, PluginManager, type PreflightConfig, type PreflightFinding, type PreflightOptions, type PreflightResult, type PreflightScanner, type Provider, type PublishOptions, type PublishResult, type PushResult, type QuotaUsage, RENDERERS, REVIEW_SORT_PRESETS, type RawCommit, type ReadinessBreakdownEntry, type ReadinessGrade, type ReadinessScore, type ReadinessSignal, type ReadinessSignalStatus, type RefundReviewPreference, type ReleaseDiff, type ReleaseNotesValidation, type ReleaseStatusResult, type Renderer, type ReportObject, type ReportsAuth, type ResolveAiConfigOptions, type ResolveLocalesOptions, type ReviewAnalysis, type ReviewExportOptions, type ReviewOrderRefundOptions, type ReviewOrderRefundResult, type ReviewSortPreset, type ReviewsFilterOptions, type RevocationRefundType, type RtdnStatus, SECTION_ORDER, SENSITIVE_ARG_KEYS, SENSITIVE_KEYS, SEVERITY_ORDER, type ScaffoldOptions, type ScaffoldResult, type SigningConsistencyResult, type SigningKeyComparison, type Spinner, type StatsReportDownload, type StatusDiff, type StatusRelease, type StatusReviews, type StatusVitalMetric, type SubscriptionAnalytics, type SubscriptionDiff, type SyncResult, type ThresholdResult, type TrainConfig, type TrainState, type TranslateBundleOptions, type TranslatedBundle, type TranslationFailure, type TranslationPath, type TranslationResult, type Translator, type TranslatorConfig, type UploadResult, VALID_WATCH_METRICS, type ValidateCheck, type ValidateOptions, type ValidateResult, type ValidatedPluginApproval, type VersionVitalsComparison, type VersionVitalsRow, type VitalsOverview, type VitalsQueryOptions, type VitalsTrendComparison, type WatchAction, type WatchCallbacks, type WatchConfig, type WatchEvent, type WatchMetric, type WatchOptions, type WatchRollout, type WatchSummary, type WatchVitalReading, type WatchVitalsOptions, type WebhookPayload, type ZipCsvEntry, abortTrain, acknowledgeProductPurchase, activateBasePlan, activateOffer, addRecoveryTargeting, addTesters, advanceTrain, analyzeBundle, analyzeRemoteListings, analyzeReviews, annotateListResult, applyReleaseNotes, assignRelease, batchGetOrders, batchSyncInAppProducts, buildChecklist, buildExternalTransactionRefund, buildLocaleBundle, bundleToReleaseNotes, cancelRecoveryAction, cancelSubscriptionPurchase, cancelSubscriptionV2, checkBundleSize, checkSigningConsistency, checkThreshold, classifyError, clearAuditLog, commitWithRescue, compareBundles, compareFingerprints, compareVersionVitals, compareVitalsTrend, computeStatusDiff, consumeProductPurchase, convertRegionPrices, createAchievementConfig, createAuditEntry, createDeviceTier, createEnterpriseApp, createExternalTransaction, createGrant, createInAppProduct, createLeaderboardConfig, createOffer, createOneTimeOffer, createOneTimeProduct, createRecoveryAction, createSpinner, createSubscription, createTrack, createTranslator, deactivateBasePlan, deactivateOffer, decodeCsvText, decodeNotification, decodeStatsCsv, defaultGitRunner, deferSubscriptionPurchase, deferSubscriptionV2, deleteAchievementConfig, deleteBasePlan, deleteGrant, deleteImage, deleteInAppProduct, deleteLeaderboardConfig, deleteListing, deleteOffer, deleteOneTimeOffer, deleteOneTimeProduct, deleteSubscription, deployRecoveryAction, detectFastlane, detectOutputFormat, diffAchievementConfig, diffLeaderboardConfig, diffListings, diffListingsCommand, diffListingsEnhanced, diffOneTimeProduct, diffReleases, diffSubscription, discoverPluginEntries, discoverPlugins, dispatchWebhook, downloadFinancialReport, downloadGeneratedApk, downloadReportObject, downloadStatsReport, exportImages, exportReviews, extractCsvEntriesFromZip, fetchAggregateCost, fetchChangelog, fetchReleaseNotes, findBundle, formatChangelogEntry, formatCustomPayload, formatDiscordPayload, formatJunit, formatMoney, formatNotification, formatOutput, formatPathLabel, formatSlackPayload, formatStatusDiff, formatStatusSummary, formatStatusTable, formatWordDiff, generateChangelog, generateMigrationPlan, generateNotesFromGit, getAchievementConfig, getAllScannerNames, getApiSigningFingerprint, getAppInfo, getAppStatus, getCountryAvailability, getDeviceTier, getExternalTransaction, getInAppProduct, getKeystoreFingerprint, getLeaderboardConfig, getListings, getOffer, getOneTimeOffer, getOneTimeProduct, getOrderDetails, getProductPurchase, getProductPurchaseV2, getQuotaUsage, getReleasesStatus, getReview, getRtdnStatus, getSubscription, getSubscriptionAnalytics, getSubscriptionPurchase, getTrainStatus, getUser, getVitalsAnomalies, getVitalsAnr, getVitalsBattery, getVitalsCrashes, getVitalsErrorCount, getVitalsLmk, getVitalsMemory, getVitalsMemoryBitmap, getVitalsMemoryRss, getVitalsOverview, getVitalsRendering, getVitalsStartup, gradeFromPercent, gunzipIfNeeded, handleBreach, importDataSafety, importTestersFromCsv, initAudit, initProject, inviteUser, isFinancialReportType, isGzip, isStatsReportType, isValidBcp47, isValidReportType, isValidStatsDimension, isVersionedNotesDir, isZip, lintListing, lintListings, lintLocalListings, listAchievementConfigs, listAchievements, listAuditEvents, listBundles, listDeviceTiers, listGeneratedApks, listGrants, listImages, listInAppProducts, listLeaderboardConfigs, listLeaderboards, listOffers, listOneTimeOffers, listOneTimeProducts, listRecoveryActions, listReportObjects, listReports, listReviews, listSubscriptions, listTesters, listTracks, listUsers, listVoidedPurchases, loadPreflightConfig, loadStatusCache, maybePaginate, migratePrices, monthToken, moreResultsFooter, normalizeFingerprint, parseAppfile, parseCommit, parseFastfile, parseGrantArg, parseKeytoolOutput, parseMonth, parseRemoteUrl, pauseTrain, promoteRelease, publish, publishEnterpriseApp, pullAchievementConfigs, pullLeaderboardConfigs, pullListings, pushAchievementConfigs, pushLeaderboardConfigs, pushListings, readListingsFromDir, readReleaseNotesForVersion, readReleaseNotesFromDir, readinessBadgeColor, readinessBadgeMarkdown, readinessBadgeUrl, redactAuditArgs, redactSensitive, refundExternalTransaction, refundOrder, relativeTime, removeTesters, removeUser, renderChecklistMarkdown, renderJson, renderMarkdown, renderPlayStore, renderPlayStoreJson, renderPlayStoreMd, renderPlayStorePrompt, renderPrompt, replyToReview, reportAccessDeniedError, reportBucketNotFoundError, reportObjectNotFoundError, reportPrefix, resolveAiConfig, resolveLocales, resolveReportsBucket, retryOnUploadNotComplete, reviewOrderRefund, revokeSubscriptionPurchase, runPreflight, runWatch, runWatchLoop, safePath, safePathWithin, saveStatusCache, scaffoldPlugin, scoreReadiness, searchAuditEvents, searchVitalsErrors, sendNotification, sendWebhook, setAchievementIcon, setLeaderboardIcon, sha256File, sortResults, sortReviews, startTrain, statusHasBreach, syncImages, syncInAppProducts, topFiles, trackBreachState, translateBundle, updateAchievementConfig, updateAppDetails, updateDataSafety, updateGrant, updateInAppProduct, updateLeaderboardConfig, updateListing, updateOffer, updateOneTimeOffer, updateOneTimeProduct, updateRollout, updateSubscription, updateTrackConfig, updateUser, uploadExternallyHosted, uploadImage, uploadInternalSharing, uploadRelease, validateAndCommit, validateBundleForApply, validateImage, validateLanguageCode, validatePackageName, validatePluginForApproval, validatePreSubmission, validateReleaseNotes, validateSku, validateTrackName, validateUploadFile, validateVersionCode, waitForBundle, waitForBundleProcessing, watchVitalsWithAutoHalt, wordDiff, writeAuditLog, writeListingsToDir, writeMigrationOutput };
|
package/dist/index.js
CHANGED
|
@@ -4551,7 +4551,7 @@ async function listUsers(client, developerId, options) {
|
|
|
4551
4551
|
if (options?.limit || options?.nextPage) {
|
|
4552
4552
|
const result = await paginateAll5(
|
|
4553
4553
|
async (pageToken) => {
|
|
4554
|
-
const resp = await client.list(developerId, { pageToken
|
|
4554
|
+
const resp = await client.list(developerId, { pageToken });
|
|
4555
4555
|
return { items: resp.users || [], nextPageToken: resp.nextPageToken };
|
|
4556
4556
|
},
|
|
4557
4557
|
{ limit: options.limit, startPageToken: options.nextPage }
|
|
@@ -4880,6 +4880,88 @@ async function getExternalTransaction(client, packageName, transactionId) {
|
|
|
4880
4880
|
async function refundExternalTransaction(client, packageName, transactionId, refundData) {
|
|
4881
4881
|
return client.externalTransactions.refund(packageName, transactionId, refundData);
|
|
4882
4882
|
}
|
|
4883
|
+
var RFC3339_RE = /^(\d{4}-\d{2}-\d{2})T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/i;
|
|
4884
|
+
var MAX_INT64 = 2n ** 63n - 1n;
|
|
4885
|
+
var FUTURE_SKEW_MS = 5 * 60 * 1e3;
|
|
4886
|
+
function refundUsageError(message, suggestion) {
|
|
4887
|
+
return new GpcError(message, "EXT_TXN_REFUND_INVALID", 2, suggestion);
|
|
4888
|
+
}
|
|
4889
|
+
function buildExternalTransactionRefund(options, now = /* @__PURE__ */ new Date()) {
|
|
4890
|
+
const partialAmount = options.partialAmount?.trim();
|
|
4891
|
+
const currency = options.currency?.trim();
|
|
4892
|
+
const refundId = options.refundId?.trim();
|
|
4893
|
+
if (options.full && partialAmount) {
|
|
4894
|
+
throw refundUsageError(
|
|
4895
|
+
"Use --full or --partial-amount, not both",
|
|
4896
|
+
"Pass --full to refund the whole transaction, or --partial-amount for part of it."
|
|
4897
|
+
);
|
|
4898
|
+
}
|
|
4899
|
+
if (!options.full && !partialAmount) {
|
|
4900
|
+
throw refundUsageError(
|
|
4901
|
+
"Choose the refund type with --full or --partial-amount",
|
|
4902
|
+
"Pass --full, or --partial-amount <micros> --currency <code> --refund-id <id>."
|
|
4903
|
+
);
|
|
4904
|
+
}
|
|
4905
|
+
let refundTime = now.toISOString();
|
|
4906
|
+
if (options.refundTime !== void 0) {
|
|
4907
|
+
const raw = options.refundTime.trim();
|
|
4908
|
+
const match = RFC3339_RE.exec(raw);
|
|
4909
|
+
const parsed = new Date(raw);
|
|
4910
|
+
const offsetMs = parsed.getTime() - Date.parse(raw.replace(/(?:Z|[+-]\d{2}:\d{2})$/i, "Z"));
|
|
4911
|
+
const calendarDate = match ? new Date(parsed.getTime() - offsetMs).toISOString().slice(0, 10) : void 0;
|
|
4912
|
+
if (!match || Number.isNaN(parsed.getTime()) || calendarDate !== match[1]) {
|
|
4913
|
+
throw refundUsageError(
|
|
4914
|
+
`Invalid --refund-time "${options.refundTime}"`,
|
|
4915
|
+
"Use an RFC 3339 timestamp with a timezone, for example 2026-09-25T12:00:00Z or 2026-09-25T14:00:00+02:00."
|
|
4916
|
+
);
|
|
4917
|
+
}
|
|
4918
|
+
if (parsed.getTime() > now.getTime() + FUTURE_SKEW_MS) {
|
|
4919
|
+
throw refundUsageError(
|
|
4920
|
+
`--refund-time "${options.refundTime}" is in the future`,
|
|
4921
|
+
"Give the time the refund actually happened, or omit --refund-time to use now."
|
|
4922
|
+
);
|
|
4923
|
+
}
|
|
4924
|
+
refundTime = parsed.toISOString();
|
|
4925
|
+
}
|
|
4926
|
+
if (options.full) {
|
|
4927
|
+
if (currency || refundId) {
|
|
4928
|
+
throw refundUsageError(
|
|
4929
|
+
"--currency and --refund-id only apply to --partial-amount",
|
|
4930
|
+
"Remove them for a full refund, or use --partial-amount instead of --full."
|
|
4931
|
+
);
|
|
4932
|
+
}
|
|
4933
|
+
return { refundTime, fullRefund: {} };
|
|
4934
|
+
}
|
|
4935
|
+
if (!partialAmount || !/^\d{1,19}$/.test(partialAmount) || BigInt(partialAmount) <= 0n || BigInt(partialAmount) > MAX_INT64) {
|
|
4936
|
+
throw refundUsageError(
|
|
4937
|
+
`Invalid --partial-amount "${options.partialAmount}"`,
|
|
4938
|
+
"Give the pre-tax amount in micros as a positive whole number, for example 1990000 for 1.99."
|
|
4939
|
+
);
|
|
4940
|
+
}
|
|
4941
|
+
if (!currency || !/^[A-Za-z]{3}$/.test(currency)) {
|
|
4942
|
+
throw refundUsageError(
|
|
4943
|
+
currency ? `Invalid --currency "${currency}"` : "--currency is required with --partial-amount",
|
|
4944
|
+
"Use the transaction's ISO 4217 currency code, for example USD."
|
|
4945
|
+
);
|
|
4946
|
+
}
|
|
4947
|
+
if (!refundId) {
|
|
4948
|
+
throw refundUsageError(
|
|
4949
|
+
"--refund-id is required with --partial-amount",
|
|
4950
|
+
"Give each partial refund a unique ID; Google rejects a second refund with the same ID."
|
|
4951
|
+
);
|
|
4952
|
+
}
|
|
4953
|
+
return {
|
|
4954
|
+
refundTime,
|
|
4955
|
+
partialRefund: {
|
|
4956
|
+
refundId,
|
|
4957
|
+
// Normalized, so "007" is sent as "7".
|
|
4958
|
+
refundPreTaxAmount: {
|
|
4959
|
+
priceMicros: BigInt(partialAmount).toString(),
|
|
4960
|
+
currency: currency.toUpperCase()
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
};
|
|
4964
|
+
}
|
|
4883
4965
|
|
|
4884
4966
|
// src/commands/device-tiers.ts
|
|
4885
4967
|
async function listDeviceTiers(client, packageName) {
|
|
@@ -5832,7 +5914,7 @@ async function initProject(options) {
|
|
|
5832
5914
|
{
|
|
5833
5915
|
failOn: "error",
|
|
5834
5916
|
targetSdkMinimum: 36,
|
|
5835
|
-
maxDownloadSizeMb:
|
|
5917
|
+
maxDownloadSizeMb: 200,
|
|
5836
5918
|
allowedPermissions: [],
|
|
5837
5919
|
disabledRules: [],
|
|
5838
5920
|
severityOverrides: {}
|
|
@@ -5950,7 +6032,7 @@ var SEVERITY_ORDER = {
|
|
|
5950
6032
|
var DEFAULT_PREFLIGHT_CONFIG = {
|
|
5951
6033
|
failOn: "error",
|
|
5952
6034
|
targetSdkMinimum: 36,
|
|
5953
|
-
maxDownloadSizeMb:
|
|
6035
|
+
maxDownloadSizeMb: 200,
|
|
5954
6036
|
allowedPermissions: [],
|
|
5955
6037
|
disabledRules: [],
|
|
5956
6038
|
severityOverrides: {}
|
|
@@ -6083,13 +6165,23 @@ function getSchema() {
|
|
|
6083
6165
|
return cachedSchema;
|
|
6084
6166
|
}
|
|
6085
6167
|
function decodeManifest(buf) {
|
|
6168
|
+
return extractManifestData(decodeManifestElement(buf));
|
|
6169
|
+
}
|
|
6170
|
+
function isInstallTimeModule(buf) {
|
|
6171
|
+
const module = getChildren(decodeManifestElement(buf), "module")[0];
|
|
6172
|
+
if (!module) return true;
|
|
6173
|
+
const delivery = getChildren(module, "delivery")[0];
|
|
6174
|
+
if (delivery) return getChildren(delivery, "install-time").length > 0;
|
|
6175
|
+
return !getBoolByName(module.attribute || [], "onDemand", false);
|
|
6176
|
+
}
|
|
6177
|
+
function decodeManifestElement(buf) {
|
|
6086
6178
|
const root = getSchema();
|
|
6087
6179
|
const XmlNode = root.lookupType("aapt.pb.XmlNode");
|
|
6088
6180
|
const decoded = XmlNode.decode(buf);
|
|
6089
6181
|
if (!decoded.element || decoded.element.name !== "manifest") {
|
|
6090
6182
|
throw new Error("Invalid AAB manifest: root element is not <manifest>");
|
|
6091
6183
|
}
|
|
6092
|
-
return
|
|
6184
|
+
return decoded.element;
|
|
6093
6185
|
}
|
|
6094
6186
|
function getAttrByName(attrs, name) {
|
|
6095
6187
|
const attr = attrs.find((a) => a.name === name || RESOURCE_IDS[a.resourceId] === name);
|
|
@@ -6198,6 +6290,9 @@ var AAB_MANIFEST_PATH = "base/manifest/AndroidManifest.xml";
|
|
|
6198
6290
|
var APK_MANIFEST_PATH = "AndroidManifest.xml";
|
|
6199
6291
|
var SO_HEADER_BYTES = 4096;
|
|
6200
6292
|
var SO_PATH_RE = /(?:^|\/)lib\/[^/]+\/[^/]+\.so$/;
|
|
6293
|
+
var MODULE_MANIFEST_RE = /^([^/]+)\/manifest\/AndroidManifest\.xml$/;
|
|
6294
|
+
var MAX_MODULE_MANIFEST_BYTES = 1024 * 1024;
|
|
6295
|
+
var MAX_MODULE_MANIFESTS = 256;
|
|
6201
6296
|
var SCAN_TIMEOUT_MS = 6e4;
|
|
6202
6297
|
function detectManifestPath(filePath) {
|
|
6203
6298
|
return filePath.toLowerCase().endsWith(".apk") ? APK_MANIFEST_PATH : AAB_MANIFEST_PATH;
|
|
@@ -6211,7 +6306,10 @@ async function readAab(aabPath) {
|
|
|
6211
6306
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6212
6307
|
throw new Error(`Could not read ${aabPath}: ${msg}`, { cause: err });
|
|
6213
6308
|
}
|
|
6214
|
-
const { zipfile, entries, manifestBuf, soHeaders } = await openAndScan(
|
|
6309
|
+
const { zipfile, entries, manifestBuf, soHeaders, moduleManifests } = await openAndScan(
|
|
6310
|
+
buffer,
|
|
6311
|
+
manifestPath
|
|
6312
|
+
);
|
|
6215
6313
|
zipfile.close();
|
|
6216
6314
|
if (!manifestBuf) {
|
|
6217
6315
|
const fileType = aabPath.toLowerCase().endsWith(".apk") ? "APK" : "AAB";
|
|
@@ -6227,7 +6325,20 @@ async function readAab(aabPath) {
|
|
|
6227
6325
|
manifest = createFallbackManifest();
|
|
6228
6326
|
manifest._parseError = `Manifest could not be fully parsed: ${errMsg}. Manifest-dependent checks will be skipped.`;
|
|
6229
6327
|
}
|
|
6230
|
-
|
|
6328
|
+
const deferredModules = [...moduleManifests].filter(([, buf]) => {
|
|
6329
|
+
try {
|
|
6330
|
+
return !isInstallTimeModule(buf);
|
|
6331
|
+
} catch {
|
|
6332
|
+
return false;
|
|
6333
|
+
}
|
|
6334
|
+
}).map(([name]) => name);
|
|
6335
|
+
return {
|
|
6336
|
+
manifest,
|
|
6337
|
+
entries,
|
|
6338
|
+
nativeLibHeaders: soHeaders,
|
|
6339
|
+
isAppBundle: manifestPath === AAB_MANIFEST_PATH,
|
|
6340
|
+
deferredModules
|
|
6341
|
+
};
|
|
6231
6342
|
}
|
|
6232
6343
|
function createFallbackManifest() {
|
|
6233
6344
|
return {
|
|
@@ -6277,6 +6388,8 @@ function openAndScan(buffer, manifestPath = AAB_MANIFEST_PATH) {
|
|
|
6277
6388
|
}
|
|
6278
6389
|
const entries = [];
|
|
6279
6390
|
const soHeaders = /* @__PURE__ */ new Map();
|
|
6391
|
+
const moduleManifests = /* @__PURE__ */ new Map();
|
|
6392
|
+
let moduleManifestReads = 0;
|
|
6280
6393
|
let manifestBuf = null;
|
|
6281
6394
|
let pendingStreams = 0;
|
|
6282
6395
|
let entrysDone = false;
|
|
@@ -6290,19 +6403,29 @@ function openAndScan(buffer, manifestPath = AAB_MANIFEST_PATH) {
|
|
|
6290
6403
|
}
|
|
6291
6404
|
function maybeResolve() {
|
|
6292
6405
|
if (!settled && entrysDone && pendingStreams === 0) {
|
|
6293
|
-
settleResolve({ zipfile, entries, manifestBuf, soHeaders });
|
|
6406
|
+
settleResolve({ zipfile, entries, manifestBuf, soHeaders, moduleManifests });
|
|
6294
6407
|
}
|
|
6295
6408
|
}
|
|
6296
|
-
function readEntryStream(entry, onData, maxBytes) {
|
|
6409
|
+
function readEntryStream(entry, onData, maxBytes, optional = false) {
|
|
6297
6410
|
pendingStreams++;
|
|
6411
|
+
let done = false;
|
|
6412
|
+
const onError = (e) => {
|
|
6413
|
+
if (!optional) {
|
|
6414
|
+
fail(e);
|
|
6415
|
+
return;
|
|
6416
|
+
}
|
|
6417
|
+
if (done) return;
|
|
6418
|
+
done = true;
|
|
6419
|
+
pendingStreams--;
|
|
6420
|
+
maybeResolve();
|
|
6421
|
+
};
|
|
6298
6422
|
zipfile.openReadStream(entry, (streamErr, stream) => {
|
|
6299
6423
|
if (streamErr || !stream) {
|
|
6300
|
-
|
|
6424
|
+
onError(streamErr ?? new Error(`Failed to read ${entry.fileName}`));
|
|
6301
6425
|
return;
|
|
6302
6426
|
}
|
|
6303
6427
|
const chunks = [];
|
|
6304
6428
|
let totalBytes = 0;
|
|
6305
|
-
let done = false;
|
|
6306
6429
|
stream.on("data", (chunk) => {
|
|
6307
6430
|
chunks.push(chunk);
|
|
6308
6431
|
totalBytes += chunk.length;
|
|
@@ -6312,7 +6435,7 @@ function openAndScan(buffer, manifestPath = AAB_MANIFEST_PATH) {
|
|
|
6312
6435
|
});
|
|
6313
6436
|
stream.on("error", (e) => {
|
|
6314
6437
|
if (maxBytes && totalBytes >= maxBytes) return;
|
|
6315
|
-
|
|
6438
|
+
onError(e);
|
|
6316
6439
|
});
|
|
6317
6440
|
const finish = () => {
|
|
6318
6441
|
if (done) return;
|
|
@@ -6340,10 +6463,22 @@ function openAndScan(buffer, manifestPath = AAB_MANIFEST_PATH) {
|
|
|
6340
6463
|
uncompressedSize: entry.uncompressedSize
|
|
6341
6464
|
});
|
|
6342
6465
|
}
|
|
6466
|
+
const moduleName = manifestPath === AAB_MANIFEST_PATH ? MODULE_MANIFEST_RE.exec(path)?.[1] : void 0;
|
|
6467
|
+
const readModuleManifest = moduleName !== void 0 && entry.uncompressedSize <= MAX_MODULE_MANIFEST_BYTES && moduleManifestReads < MAX_MODULE_MANIFESTS;
|
|
6343
6468
|
if (path === manifestPath) {
|
|
6344
6469
|
readEntryStream(entry, (buf) => {
|
|
6345
6470
|
manifestBuf = buf;
|
|
6346
6471
|
});
|
|
6472
|
+
} else if (moduleName !== void 0 && readModuleManifest) {
|
|
6473
|
+
moduleManifestReads++;
|
|
6474
|
+
readEntryStream(
|
|
6475
|
+
entry,
|
|
6476
|
+
(buf) => {
|
|
6477
|
+
moduleManifests.set(moduleName, buf);
|
|
6478
|
+
},
|
|
6479
|
+
MAX_MODULE_MANIFEST_BYTES,
|
|
6480
|
+
true
|
|
6481
|
+
);
|
|
6347
6482
|
} else if (SO_PATH_RE.test(path)) {
|
|
6348
6483
|
readEntryStream(
|
|
6349
6484
|
entry,
|
|
@@ -6457,20 +6592,18 @@ var manifestScanner = {
|
|
|
6457
6592
|
}
|
|
6458
6593
|
if (manifest.targetSdk >= 34) {
|
|
6459
6594
|
const hasFgsPerm = manifest.permissions.includes("android.permission.FOREGROUND_SERVICE");
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
}
|
|
6473
|
-
}
|
|
6595
|
+
const anyTyped = manifest.services.some((s) => s.foregroundServiceType);
|
|
6596
|
+
if (hasFgsPerm && !anyTyped && manifest.services.length > 0) {
|
|
6597
|
+
const typedPerms = manifest.permissions.filter((p) => p.startsWith("android.permission.FOREGROUND_SERVICE_")).map((p) => p.replace("android.permission.", ""));
|
|
6598
|
+
findings.push({
|
|
6599
|
+
scanner: "manifest",
|
|
6600
|
+
ruleId: "foreground-service-type-missing",
|
|
6601
|
+
severity: "warning",
|
|
6602
|
+
title: "No service declares a foregroundServiceType",
|
|
6603
|
+
message: `The app requests FOREGROUND_SERVICE${typedPerms.length > 0 ? ` (and ${typedPerms.join(", ")})` : ""} but none of its ${manifest.services.length} services declares android:foregroundServiceType. On API 34+, any service that calls startForeground() must declare a type or it crashes at runtime. Services that never run in the foreground do not need one.`,
|
|
6604
|
+
suggestion: 'Add android:foregroundServiceType to each service you start in the foreground (for example dataSync, mediaPlayback, location). If none do, remove the FOREGROUND_SERVICE permission or add this rule to "disabledRules".',
|
|
6605
|
+
policyUrl: "https://developer.android.com/about/versions/14/changes/fgs-types-required"
|
|
6606
|
+
});
|
|
6474
6607
|
}
|
|
6475
6608
|
}
|
|
6476
6609
|
const hasBackgroundLocation = manifest.permissions.includes(
|
|
@@ -6788,9 +6921,36 @@ var permissionsScanner = {
|
|
|
6788
6921
|
}
|
|
6789
6922
|
};
|
|
6790
6923
|
|
|
6924
|
+
// src/preflight/bundle-layout.ts
|
|
6925
|
+
var LIB_PATH_RE = /^(?:[^/]+\/)?lib\/([^/]+)\/[^/]+\.so$/;
|
|
6926
|
+
function isBundleMetadata(path) {
|
|
6927
|
+
return path.startsWith("BUNDLE-METADATA/") || path.startsWith("META-INF/") || path === "BundleConfig.pb";
|
|
6928
|
+
}
|
|
6929
|
+
function nativeLibAbi(path) {
|
|
6930
|
+
return LIB_PATH_RE.exec(path)?.[1];
|
|
6931
|
+
}
|
|
6932
|
+
function firstInstallEntries(entries, isAppBundle, deferredModules = []) {
|
|
6933
|
+
if (!isAppBundle) return [...entries];
|
|
6934
|
+
const deferred = new Set(deferredModules);
|
|
6935
|
+
return entries.filter(
|
|
6936
|
+
(e) => !isBundleMetadata(e.path) && !deferred.has(e.path.split("/", 1)[0] ?? "")
|
|
6937
|
+
);
|
|
6938
|
+
}
|
|
6939
|
+
function largestAbi(entries, size) {
|
|
6940
|
+
const perAbi = /* @__PURE__ */ new Map();
|
|
6941
|
+
for (const entry of entries) {
|
|
6942
|
+
const abi = nativeLibAbi(entry.path);
|
|
6943
|
+
if (abi) perAbi.set(abi, (perAbi.get(abi) ?? 0) + size(entry));
|
|
6944
|
+
}
|
|
6945
|
+
let largest;
|
|
6946
|
+
for (const [abi, bytes] of perAbi) {
|
|
6947
|
+
if (!largest || bytes > largest.bytes) largest = { abi, bytes };
|
|
6948
|
+
}
|
|
6949
|
+
return largest;
|
|
6950
|
+
}
|
|
6951
|
+
|
|
6791
6952
|
// src/preflight/scanners/native-libs-scanner.ts
|
|
6792
6953
|
var KNOWN_ABIS = ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"];
|
|
6793
|
-
var LIB_PATH_RE = /^(?:[^/]+\/)?lib\/([^/]+)\/[^/]+\.so$/;
|
|
6794
6954
|
var ELF_MAGIC = 2135247942;
|
|
6795
6955
|
var PT_LOAD = 1;
|
|
6796
6956
|
var PAGE_16KB = 16384;
|
|
@@ -6848,7 +7008,7 @@ var nativeLibsScanner = {
|
|
|
6848
7008
|
const abisFound = /* @__PURE__ */ new Set();
|
|
6849
7009
|
let totalNativeSize = 0;
|
|
6850
7010
|
for (const entry of entries) {
|
|
6851
|
-
const abi =
|
|
7011
|
+
const abi = nativeLibAbi(entry.path);
|
|
6852
7012
|
if (abi) {
|
|
6853
7013
|
abisFound.add(abi);
|
|
6854
7014
|
totalNativeSize += entry.uncompressedSize;
|
|
@@ -6897,14 +7057,21 @@ var nativeLibsScanner = {
|
|
|
6897
7057
|
title: `Native libraries: ${abiList}`,
|
|
6898
7058
|
message: `Found native libraries for ${abisFound.size} architecture(s): ${abiList}. Total uncompressed size: ${sizeMb} MB.`
|
|
6899
7059
|
});
|
|
6900
|
-
|
|
7060
|
+
const bundle = ctx.isAppBundle ?? false;
|
|
7061
|
+
const perDevice = bundle ? largestAbi(
|
|
7062
|
+
firstInstallEntries(entries, true, ctx.deferredModules),
|
|
7063
|
+
(e) => e.uncompressedSize
|
|
7064
|
+
) : void 0;
|
|
7065
|
+
const perDeviceSize = bundle ? perDevice?.bytes ?? 0 : totalNativeSize;
|
|
7066
|
+
if (perDeviceSize > 150 * 1024 * 1024) {
|
|
7067
|
+
const perDeviceMb = (perDeviceSize / (1024 * 1024)).toFixed(1);
|
|
6901
7068
|
findings.push({
|
|
6902
7069
|
scanner: "native-libs",
|
|
6903
7070
|
ruleId: "native-libs-large",
|
|
6904
7071
|
severity: "warning",
|
|
6905
7072
|
title: "Large native libraries",
|
|
6906
|
-
message: `Native libraries total ${sizeMb} MB (uncompressed). This significantly increases download size.`,
|
|
6907
|
-
suggestion: "Consider using Android App Bundles to deliver only the required ABI per device. Review if all native libraries are necessary."
|
|
7073
|
+
message: perDevice ? `Native libraries for ${perDevice.abi} total ${perDeviceMb} MB (uncompressed). Each device on that ABI downloads all of them.` : `Native libraries total ${sizeMb} MB (uncompressed). This significantly increases download size.`,
|
|
7074
|
+
suggestion: perDevice ? "Review whether all native libraries are necessary, and move optional native code into on-demand feature modules." : "Consider using Android App Bundles to deliver only the required ABI per device. Review if all native libraries are necessary."
|
|
6908
7075
|
});
|
|
6909
7076
|
}
|
|
6910
7077
|
return findings;
|
|
@@ -7559,6 +7726,9 @@ var policyScanner = {
|
|
|
7559
7726
|
};
|
|
7560
7727
|
|
|
7561
7728
|
// src/preflight/scanners/size-scanner.ts
|
|
7729
|
+
var MB = 1024 * 1024;
|
|
7730
|
+
var PLAY_MOBILE_DATA_DIALOG_MB = 200;
|
|
7731
|
+
var mb = (bytes) => (bytes / MB).toFixed(1);
|
|
7562
7732
|
var sizeScanner = {
|
|
7563
7733
|
name: "size",
|
|
7564
7734
|
description: "Analyzes app bundle size and warns on large downloads",
|
|
@@ -7572,21 +7742,28 @@ var sizeScanner = {
|
|
|
7572
7742
|
const maxMb = ctx.config.maxDownloadSizeMb;
|
|
7573
7743
|
const totalCompressed = entries.reduce((sum, e) => sum + e.compressedSize, 0);
|
|
7574
7744
|
const totalUncompressed = entries.reduce((sum, e) => sum + e.uncompressedSize, 0);
|
|
7575
|
-
const
|
|
7576
|
-
const
|
|
7577
|
-
|
|
7745
|
+
const bundle = ctx.isAppBundle ?? false;
|
|
7746
|
+
const installed = firstInstallEntries(entries, bundle, ctx.deferredModules);
|
|
7747
|
+
const nativeAbi = bundle ? largestAbi(installed, (e) => e.compressedSize) : void 0;
|
|
7748
|
+
const delivered = bundle ? installed.filter((e) => {
|
|
7749
|
+
const abi = nativeLibAbi(e.path);
|
|
7750
|
+
return !abi || abi === nativeAbi?.abi;
|
|
7751
|
+
}) : installed;
|
|
7752
|
+
const downloadBytes = delivered.reduce((sum, e) => sum + e.compressedSize, 0);
|
|
7753
|
+
if (downloadBytes / MB > maxMb) {
|
|
7754
|
+
const size = bundle ? `Estimated download for one device is ${mb(downloadBytes)} MB${nativeAbi ? ` (with ${nativeAbi.abi} native libraries)` : ""}` : `The APK is ${mb(downloadBytes)} MB compressed`;
|
|
7578
7755
|
findings.push({
|
|
7579
7756
|
scanner: "size",
|
|
7580
7757
|
ruleId: "size-over-limit",
|
|
7581
7758
|
severity: "warning",
|
|
7582
7759
|
title: `Download size exceeds ${maxMb} MB`,
|
|
7583
|
-
message:
|
|
7584
|
-
suggestion: "
|
|
7585
|
-
policyUrl: "https://
|
|
7760
|
+
message: `${size}, above your ${maxMb} MB threshold.` + (downloadBytes > PLAY_MOBILE_DATA_DIALOG_MB * MB ? ` Google Play shows users on mobile data a large-download dialog for apps over ${PLAY_MOBILE_DATA_DIALOG_MB} MB, which can reduce install rates.` : ""),
|
|
7761
|
+
suggestion: bundle ? "Remove unused resources, enable R8, and move large or optional content into fast-follow or on-demand asset packs or feature modules." : "Publish an Android App Bundle so each device downloads only its own ABI and screen density, remove unused resources, and enable R8.",
|
|
7762
|
+
policyUrl: "https://support.google.com/googleplay/android-developer/answer/9859372"
|
|
7586
7763
|
});
|
|
7587
7764
|
}
|
|
7588
7765
|
const categories = /* @__PURE__ */ new Map();
|
|
7589
|
-
for (const entry of
|
|
7766
|
+
for (const entry of delivered) {
|
|
7590
7767
|
const cat = detectCategory(entry.path);
|
|
7591
7768
|
const existing = categories.get(cat) ?? { compressed: 0, uncompressed: 0, count: 0 };
|
|
7592
7769
|
existing.compressed += entry.compressedSize;
|
|
@@ -7595,13 +7772,13 @@ var sizeScanner = {
|
|
|
7595
7772
|
categories.set(cat, existing);
|
|
7596
7773
|
}
|
|
7597
7774
|
const nativeLibs = categories.get("native-libs");
|
|
7598
|
-
if (nativeLibs && nativeLibs.compressed > 50 *
|
|
7775
|
+
if (nativeLibs && nativeLibs.compressed > 50 * MB) {
|
|
7599
7776
|
findings.push({
|
|
7600
7777
|
scanner: "size",
|
|
7601
7778
|
ruleId: "size-large-native",
|
|
7602
7779
|
severity: "warning",
|
|
7603
7780
|
title: "Large native libraries",
|
|
7604
|
-
message: `Native libraries are ${(nativeLibs.compressed
|
|
7781
|
+
message: nativeAbi ? `Native libraries for ${nativeAbi.abi} are ${mb(nativeLibs.compressed)} MB (compressed), and each device on that ABI downloads all of them.` : `Native libraries are ${mb(nativeLibs.compressed)} MB (compressed) across every ABI in the APK.`,
|
|
7605
7782
|
suggestion: "Review which native libraries are bundled. Consider using dynamic feature modules for optional native code."
|
|
7606
7783
|
});
|
|
7607
7784
|
}
|
|
@@ -7612,18 +7789,18 @@ var sizeScanner = {
|
|
|
7612
7789
|
ruleId: "size-large-assets",
|
|
7613
7790
|
severity: "info",
|
|
7614
7791
|
title: "Large assets directory",
|
|
7615
|
-
message: `Assets are ${(assets.compressed
|
|
7792
|
+
message: `Assets are ${mb(assets.compressed)} MB (compressed). Consider using Play Asset Delivery for large assets.`,
|
|
7616
7793
|
suggestion: "Move large assets to Play Asset Delivery (install-time, fast-follow, or on-demand packs).",
|
|
7617
7794
|
policyUrl: "https://developer.android.com/guide/playcore/asset-delivery"
|
|
7618
7795
|
});
|
|
7619
7796
|
}
|
|
7620
|
-
const breakdown = [...categories.entries()].sort((a, b) => b[1].compressed - a[1].compressed).map(([cat, data]) => `${cat}: ${(data.compressed
|
|
7797
|
+
const breakdown = [...categories.entries()].sort((a, b) => b[1].compressed - a[1].compressed).map(([cat, data]) => `${cat}: ${mb(data.compressed)} MB`).join(", ");
|
|
7621
7798
|
findings.push({
|
|
7622
7799
|
scanner: "size",
|
|
7623
7800
|
ruleId: "size-summary",
|
|
7624
7801
|
severity: "info",
|
|
7625
|
-
title: `Total size: ${
|
|
7626
|
-
message: `${entries.length} files. Breakdown: ${breakdown}`
|
|
7802
|
+
title: bundle ? `Estimated download: ${mb(downloadBytes)} MB per device (bundle file ${mb(totalCompressed)} MB compressed, ${mb(totalUncompressed)} MB uncompressed)` : `Total size: ${mb(totalCompressed)} MB compressed, ${mb(totalUncompressed)} MB uncompressed`,
|
|
7803
|
+
message: bundle ? `${delivered.length} of ${entries.length} files reach one device. Breakdown: ${breakdown}` : `${entries.length} files. Breakdown: ${breakdown}`
|
|
7627
7804
|
});
|
|
7628
7805
|
return findings;
|
|
7629
7806
|
}
|
|
@@ -7631,10 +7808,10 @@ var sizeScanner = {
|
|
|
7631
7808
|
function detectCategory(path) {
|
|
7632
7809
|
const lower = path.toLowerCase();
|
|
7633
7810
|
if (lower.endsWith(".dex") || /\/dex\//.test(lower)) return "dex";
|
|
7634
|
-
if (
|
|
7635
|
-
if (
|
|
7811
|
+
if (nativeLibAbi(path)) return "native-libs";
|
|
7812
|
+
if (/(?:^|\/)res\//.test(lower) || /(?:^|\/)resources\.(?:pb|arsc)$/.test(lower))
|
|
7636
7813
|
return "resources";
|
|
7637
|
-
if (
|
|
7814
|
+
if (/(?:^|\/)assets\//.test(lower)) return "assets";
|
|
7638
7815
|
if (lower.includes("androidmanifest.xml") || /\/manifest\//.test(lower)) return "manifest";
|
|
7639
7816
|
if (lower.startsWith("meta-inf/")) return "signing";
|
|
7640
7817
|
return "other";
|
|
@@ -7670,6 +7847,8 @@ async function runPreflight(options) {
|
|
|
7670
7847
|
ctx.manifest = aab.manifest;
|
|
7671
7848
|
ctx.zipEntries = aab.entries;
|
|
7672
7849
|
ctx.nativeLibHeaders = aab.nativeLibHeaders;
|
|
7850
|
+
ctx.isAppBundle = aab.isAppBundle;
|
|
7851
|
+
ctx.deferredModules = aab.deferredModules;
|
|
7673
7852
|
if (aab.manifest._parseError) {
|
|
7674
7853
|
earlyFindings.push({
|
|
7675
7854
|
scanner: "manifest-parser",
|
|
@@ -10151,6 +10330,7 @@ export {
|
|
|
10151
10330
|
batchGetOrders,
|
|
10152
10331
|
batchSyncInAppProducts,
|
|
10153
10332
|
buildChecklist,
|
|
10333
|
+
buildExternalTransactionRefund,
|
|
10154
10334
|
buildLocaleBundle,
|
|
10155
10335
|
bundleToReleaseNotes,
|
|
10156
10336
|
cancelRecoveryAction,
|